/**
 *@Time    : 2021/10/15 17:46
 *@Author  : Shuai Jhou
 *@File    : access_log
 *@Software: GoLand
 */

package middleware

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"go.zhoushuai.cn/go-programming-tour-book/blog-service/global"
	"go.zhoushuai.cn/go-programming-tour-book/blog-service/pkg/app"
	"go.zhoushuai.cn/go-programming-tour-book/blog-service/pkg/errcode"
	"go.zhoushuai.cn/go-programming-tour-book/blog-service/pkg/logger"
	"time"
)

var _ = `
实际上在写入流时，调用的是 http.ResponseWriter
type ResponseWriter interface {
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)
}
那么我们只需要写一个针对访问日志的 Writer 结构体，实现我们特定的 Write 方法就可以解决无法直接取到方法响应主体的问题了.
`
type AccessLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

// 在 AccessLogWriter 的 Write 方法中，实现了双写，因此我们可以直接通过 AccessLogWriter 的 body 取到值
func (w AccessLogWriter) Write(p []byte) (int, error) {
	if n, err := w.body.Write(p); err != nil {
		return n, err
	}
	return w.ResponseWriter.Write(p)
}

func AccessLog() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 初始化 AccessLogWriter，将其赋予给当前的 Writer 写入流（可理解为替换原有）
		bodyWriter := &AccessLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		// 替换原有
		c.Writer = bodyWriter

		beginTime := time.Now().Unix()
		c.Next()
		endTime := time.Now().Unix()

		fields := logger.Fields{
			"request":  c.Request.PostForm.Encode(),
			"response": bodyWriter.body.String(),
		}
		global.Logger.WithFields(fields).Infof("access log: method: %s, status_code: %d, begin_time: %d, end_time: %d",
			c.Request.Method,
			bodyWriter.Status(),
			beginTime,
			endTime,
		)
	}
}

func Recovery() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				global.Logger.WithCallersFrames().Errorf("panic recover err: %v", err)
				app.NewRespClient(c).ErrRespClient(errcode.ServerError)
				c.Abort()
			}
		}()
		c.Next()
	}
}
